Skip to content

Method: resolveFieldPrefix(OWLEntity, Set)

1: /**
2: * Copyright (C) 2023 Czech Technical University in Prague
3: *
4: * This program is free software: you can redistribute it and/or modify it under
5: * the terms of the GNU General Public License as published by the Free Software
6: * Foundation, either version 3 of the License, or (at your option) any
7: * later version.
8: *
9: * This program is distributed in the hope that it will be useful, but WITHOUT
10: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11: * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
12: * details. You should have received a copy of the GNU General Public License
13: * along with this program. If not, see <http://www.gnu.org/licenses/>.
14: */
15: package cz.cvut.kbss.jopa.owl2java;
16:
17: import com.sun.codemodel.*;
18: import cz.cvut.kbss.jopa.ic.api.AtomicSubClassConstraint;
19: import cz.cvut.kbss.jopa.ic.api.DataParticipationConstraint;
20: import cz.cvut.kbss.jopa.ic.api.ObjectParticipationConstraint;
21: import cz.cvut.kbss.jopa.model.MultilingualString;
22: import cz.cvut.kbss.jopa.model.annotations.OWLAnnotationProperty;
23: import cz.cvut.kbss.jopa.model.annotations.OWLDataProperty;
24: import cz.cvut.kbss.jopa.model.annotations.OWLObjectProperty;
25: import cz.cvut.kbss.jopa.model.annotations.Properties;
26: import cz.cvut.kbss.jopa.model.annotations.*;
27: import cz.cvut.kbss.jopa.owl2java.cli.Option;
28: import cz.cvut.kbss.jopa.owl2java.cli.PropertiesType;
29: import cz.cvut.kbss.jopa.owl2java.config.TransformationConfiguration;
30: import cz.cvut.kbss.jopa.owl2java.exception.OWL2JavaException;
31: import cz.cvut.kbss.jopa.owlapi.DatatypeTransformer;
32: import cz.cvut.kbss.jopa.vocabulary.DC;
33: import cz.cvut.kbss.jopa.vocabulary.RDFS;
34: import org.semanticweb.owlapi.model.OWLClass;
35: import org.semanticweb.owlapi.model.*;
36: import org.semanticweb.owlapi.search.EntitySearcher;
37: import org.slf4j.Logger;
38: import org.slf4j.LoggerFactory;
39:
40: import java.io.Serializable;
41: import java.text.Normalizer;
42: import java.util.*;
43: import java.util.concurrent.atomic.AtomicBoolean;
44: import java.util.stream.Collectors;
45:
46: import static cz.cvut.kbss.jopa.owl2java.Constants.*;
47:
48: public class JavaTransformer {
49:
50: private static final Logger LOG = LoggerFactory.getLogger(OWL2JavaTransformer.class);
51:
52: private static final String[] KEYWORDS = {"abstract",
53: "assert",
54: "boolean",
55: "break",
56: "byte",
57: "case",
58: "catch",
59: "char",
60: "class",
61: "const",
62: "continue",
63: "default",
64: "do",
65: "double",
66: "else",
67: "enum",
68: "extends",
69: "final",
70: "finally",
71: "float",
72: "for",
73: "goto",
74: "if",
75: "implements",
76: "import",
77: "instanceof",
78: "int",
79: "interface",
80: "long",
81: "native",
82: "new",
83: "package",
84: "private",
85: "protected",
86: "public",
87: "return",
88: "short",
89: "static",
90: "strictfp",
91: "super",
92: "switch",
93: "synchronized",
94: "this",
95: "throw",
96: "throws",
97: "transient",
98: "try",
99: "void",
100: "volatile",
101: "while"};
102:
103: private static final String PREFIX_STRING = "s_";
104: private static final String PREFIX_CLASS = "c_";
105: private static final String PREFIX_PROPERTY = "p_";
106: private static final String PREFIX_INDIVIDUAL = "i_";
107: private static final String PREFIX_DATATYPE = "d_";
108:
109: private JDefinedClass voc;
110: private final Map<OWLEntity, JFieldRef> entities = new HashMap<>();
111:
112: private final Map<OWLClass, JDefinedClass> classes = new HashMap<>();
113:
114: private final TransformationConfiguration configuration;
115:
116: JavaTransformer(TransformationConfiguration configuration) {
117: this.configuration = configuration;
118: }
119:
120: private static String validJavaIDForIRI(final IRI iri) {
121: if (iri.getFragment() != null) {
122: return validJavaID(iri.getFragment());
123: } else {
124: int x = iri.toString().lastIndexOf("/");
125: return validJavaID(iri.toString().substring(x + 1));
126: }
127: }
128:
129: private static String validJavaID(final String s) {
130: String res = s.trim().replace("-", "_").replace("'", "_quote_").replace(".", "_dot_").replace(',', '_');
131: // Replace non-ASCII characters with ASCII ones
132: res = Normalizer.normalize(res, Normalizer.Form.NFD).replaceAll("[^\\p{ASCII}]", "");
133: if (Arrays.binarySearch(KEYWORDS, res) >= 0) {
134: res = "_" + res;
135: }
136: return res;
137: }
138:
139: private static JFieldVar addField(final String name, final JDefinedClass cls,
140: final JType fieldType) {
141: String newName = name;
142:
143: int i = 0;
144: while (cls.fields().containsKey(newName)) {
145: newName = name + "" + (++i);
146: }
147:
148: final JFieldVar fvId = cls.field(JMod.PROTECTED, fieldType, newName);
149: final String fieldName = fvId.name().substring(0, 1).toUpperCase() + fvId.name().substring(1);
150: final JMethod mSetId = cls.method(JMod.PUBLIC, void.class, "set" + fieldName);
151: final JVar v = mSetId.param(fieldType, fvId.name());
152: mSetId.body().assign(JExpr._this().ref(fvId), v);
153: final JMethod mGetId = cls.method(JMod.PUBLIC, fieldType, "get" + fieldName);
154: mGetId.body()._return(fvId);
155: return fvId;
156: }
157:
158: /**
159: * Generates an object model consisting of JOPA entity classes and a vocabulary file from the specified ontology and
160: * context definition.
161: *
162: * @param ontology Ontology from which the model is generated
163: * @param context Context information
164: * @return The generated object model
165: */
166: public ObjectModel generateModel(final OWLOntology ontology, final ContextDefinition context) {
167: try {
168: final JCodeModel cm = new JCodeModel();
169: voc = createVocabularyClass(cm);
170: generateVocabulary(ontology, cm, context);
171: generateModelImpl(ontology, cm, context, modelPackageName());
172: return new ObjectModel(cm);
173: } catch (JClassAlreadyExistsException e) {
174: throw new OWL2JavaException("Transformation FAILED.", e);
175: }
176: }
177:
178: private String modelPackageName() {
179: final String packageConfig = configuration.getPackageName();
180: final StringBuilder sb = new StringBuilder(packageConfig);
181: if (!packageConfig.isEmpty()) {
182: sb.append(PACKAGE_SEPARATOR);
183: }
184: sb.append(MODEL_PACKAGE);
185: return sb.toString();
186: }
187:
188: private JDefinedClass createVocabularyClass(JCodeModel codeModel)
189: throws JClassAlreadyExistsException {
190: final String packageName = configuration.getPackageName();
191: final String className =
192: packageName.isEmpty() ? VOCABULARY_CLASS : packageName + PACKAGE_SEPARATOR + VOCABULARY_CLASS;
193: final JDefinedClass cls = codeModel._class(className);
194: generateAuthorshipDoc(cls);
195: return cls;
196: }
197:
198: private static void generateAuthorshipDoc(JDocCommentable javaElem) {
199: javaElem.javadoc().add("This class was generated by OWL2Java " + VERSION);
200: }
201:
202: /**
203: * Generates only vocabulary of the loaded ontology.
204: *
205: * @param ontology Ontology from which the vocabulary should be generated
206: * @param context Integrity constraints context, if null is supplied, the whole ontology is interpreted as
207: * integrity constraints.
208: * @return The generated object model
209: */
210: public ObjectModel generateVocabulary(final OWLOntology ontology, ContextDefinition context) {
211: try {
212: final JCodeModel cm = new JCodeModel();
213: this.voc = createVocabularyClass(cm);
214: generateVocabulary(ontology, cm, context);
215: return new ObjectModel(cm);
216: } catch (JClassAlreadyExistsException e) {
217: throw new OWL2JavaException("Vocabulary generation FAILED, because the Vocabulary class already exists.",
218: e);
219: }
220: }
221:
222: private void generateObjectProperty(final OWLOntology ontology,
223: final JCodeModel cm,
224: final ContextDefinition context,
225: final String pkg,
226: final OWLClass clazz,
227: final JDefinedClass subj,
228: final org.semanticweb.owlapi.model.OWLObjectProperty prop) {
229: final ClassObjectPropertyComputer comp = new ClassObjectPropertyComputer(
230: clazz,
231: prop,
232: context.set,
233: ontology
234: );
235:
236: if (Card.NO != comp.getCard()) {
237: JClass filler = ensureCreated(pkg, cm, comp.getFiller(), ontology);
238: final String fieldName = validJavaIDForIRI(prop.getIRI());
239:
240: switch (comp.getCard()) {
241: case MULTIPLE:
242: filler = cm.ref(java.util.Set.class).narrow(filler);
243: break;
244: case SIMPLELIST:
245: case LIST:
246: filler = cm.ref(java.util.List.class).narrow(filler);
247: break;
248: default:
249: break;
250: }
251:
252: final JFieldVar fv = addField(fieldName, subj, filler);
253: generateJavadoc(ontology, prop, fv);
254:
255: if (comp.getCard().equals(Card.SIMPLELIST)) {
256: fv.annotate(Sequence.class).param("type", SequenceType.simple);
257: }
258:
259:
260: fv.annotate(OWLObjectProperty.class).param("iri", entities.get(prop));
261:
262: JAnnotationArrayMember use = null;
263: for (ObjectParticipationConstraint ic : comp.getParticipationConstraints()) {
264: if (use == null) {
265: use = fv.annotate(ParticipationConstraints.class).paramArray("value");
266: }
267: JAnnotationUse u = use.annotate(ParticipationConstraint.class)
268: .param("owlObjectIRI", entities.get(ic.getObject()));
269: setParticipationConstraintCardinality(u, ic);
270: }
271: }
272: }
273:
274: private static void setParticipationConstraintCardinality(JAnnotationUse u,
275: cz.cvut.kbss.jopa.ic.api.ParticipationConstraint<?, ?> ic) {
276: if (ic.getMin() != 0) {
277: u.param("min", ic.getMin());
278: }
279: if (ic.getMin() != -1) {
280: u.param("max", ic.getMax());
281: }
282: }
283:
284: private void generateDataProperty(final OWLOntology ontology,
285: final JCodeModel cm,
286: final ContextDefinition context,
287: final OWLClass clazz,
288: final JDefinedClass subj,
289: final org.semanticweb.owlapi.model.OWLDataProperty prop) {
290: final ClassDataPropertyComputer comp = new ClassDataPropertyComputer(
291: clazz,
292: prop,
293: context.set,
294: ontology
295: );
296:
297: if (Card.NO != comp.getCard()) {
298:
299: final JType obj = cm._ref(resolveFieldType(comp.getFiller()));
300:
301: final String fieldName = validJavaIDForIRI(prop.getIRI());
302:
303: JFieldVar fv;
304:
305: switch (comp.getCard()) {
306: case MULTIPLE:
307: fv = addField(fieldName, subj, cm.ref(java.util.Set.class).narrow(obj));
308: break;
309: case ONE:
310: fv = addField(fieldName, subj, obj);
311: break;
312: default:
313: throw new OWL2JavaException("Unsupported data property cardinality type " + comp.getCard());
314: }
315: generateJavadoc(ontology, prop, fv);
316:
317: fv.annotate(OWLDataProperty.class).param("iri", entities.get(prop));
318:
319: JAnnotationArrayMember use = null;
320: for (DataParticipationConstraint ic : comp.getParticipationConstraints()) {
321: if (use == null) {
322: use = fv.annotate(ParticipationConstraints.class).paramArray("value");
323: }
324:
325: JAnnotationUse u = use.annotate(ParticipationConstraint.class)
326: .param("owlObjectIRI", comp.getFiller().getIRI().toString());
327:
328: setParticipationConstraintCardinality(u, ic);
329: }
330: }
331: }
332:
333: private Class<?> resolveFieldType(OWLDatatype datatype) {
334: final Class<?> cls = DatatypeTransformer.transformOWLType(datatype);
335: if (MultilingualString.class.equals(cls) && !configuration.shouldPreferMultilingualStrings()) {
336: return String.class;
337: }
338: return cls;
339: }
340:
341: private void generateModelImpl(final OWLOntology ontology, final JCodeModel cm,
342: final ContextDefinition context, final String pkg) {
343: LOG.info("Generating model ...");
344: final PropertiesType propertiesType = configuration.getPropertiesType();
345:
346: if (configuration.shouldGenerateThing()) {
347: context.classes.add(ontology.getOWLOntologyManager().getOWLDataFactory().getOWLThing());
348: }
349:
350: for (final OWLClass clazz : context.classes) {
351: LOG.info(" Generating class '{}'.", clazz);
352: final JDefinedClass subj = ensureCreated(pkg, cm, clazz, ontology);
353:
354: final AtomicBoolean extendClass = new AtomicBoolean(false);
355: context.set.getClassIntegrityConstraints(clazz).stream()
356: .filter(ic -> ic instanceof AtomicSubClassConstraint).forEach(ic -> {
357: final AtomicSubClassConstraint icc = (AtomicSubClassConstraint) ic;
358: subj._extends(ensureCreated(pkg, cm, icc.getSupClass(), ontology));
359: extendClass.set(true);
360: });
361:
362: if (!extendClass.get()) {
363: addCommonClassFields(cm, subj, propertiesType);
364: }
365: for (final org.semanticweb.owlapi.model.OWLObjectProperty prop : context.objectProperties) {
366: generateObjectProperty(ontology, cm, context, pkg, clazz, subj, prop);
367: }
368:
369: for (org.semanticweb.owlapi.model.OWLDataProperty prop : context.dataProperties) {
370: generateDataProperty(ontology, cm, context, clazz, subj, prop);
371: }
372: }
373: }
374:
375: private void generateVocabulary(final OWLOntology o, final JCodeModel cm, ContextDefinition context) {
376: final Collection<OWLEntity> col = new LinkedHashSet<>();
377: col.add(o.getOWLOntologyManager().getOWLDataFactory().getOWLThing());
378: col.addAll(context.classes);
379: col.addAll(context.objectProperties);
380: col.addAll(context.dataProperties);
381: col.addAll(context.annotationProperties);
382: col.addAll(context.individuals);
383:
384: generateOntologyIrisConstants(o.getOWLOntologyManager());
385:
386: final Set<IRI> visitedProperties = new HashSet<>(col.size());
387:
388: for (final OWLEntity c : col) {
389: final Optional<String> prefix = resolveFieldPrefix(c, visitedProperties);
390: if (prefix.isEmpty()) {
391: continue;
392: }
393: final String sFieldName = ensureUniqueIdentifier(
394: PREFIX_STRING + prefix.get() + validJavaIDForIRI(c.getIRI()));
395:
396: final JFieldVar fv1 = voc.field(JMod.PUBLIC | JMod.STATIC
397: | JMod.FINAL, String.class, sFieldName, JExpr.lit(c.getIRI().toString()));
398: if (configuration.shouldGenerateOwlapiIris()) {
399: voc.field(JMod.PUBLIC | JMod.STATIC | JMod.FINAL, IRI.class,
400: sFieldName.substring(PREFIX_STRING.length()),
401: cm.ref(IRI.class).staticInvoke("create").arg(fv1));
402: }
403: generateJavadoc(o, c, fv1);
404: entities.put(c, voc.staticRef(fv1));
405: }
406: }
407:
408: private void generateOntologyIrisConstants(OWLOntologyManager ontologyManager) {
409: // Get only unique ontology IRIs sorted
410: final List<IRI> ontologyIris = ontologyManager.ontologies().map(o -> o.getOntologyID().getOntologyIRI())
411: .filter(Optional::isPresent).map(Optional::get).distinct()
412: .sorted(Comparator.comparing(IRI::getIRIString))
413: .collect(Collectors.toList());
414: ontologyIris.forEach(iri -> {
415: final String fieldName = ensureUniqueIdentifier("ONTOLOGY_IRI_" + validJavaIDForIRI(iri));
416: voc.field(JMod.PUBLIC | JMod.STATIC | JMod.FINAL, String.class, fieldName, JExpr.lit(iri.toString()));
417: });
418: }
419:
420: private static Optional<String> resolveFieldPrefix(OWLEntity c, Set<IRI> visitedProperties) {
421:• if (c.isOWLClass()) {
422: return Optional.of(PREFIX_CLASS);
423:• } else if (c.isOWLDatatype()) {
424: return Optional.of(PREFIX_DATATYPE);
425:• } else if (c.isOWLDataProperty() || c.isOWLObjectProperty() || c.isOWLAnnotationProperty()) {
426:• if (visitedProperties.contains(c.getIRI())) {
427: LOG.debug("Property with IRI {} already processed. Skipping.", c.getIRI());
428: return Optional.empty();
429: }
430: visitedProperties.add(c.getIRI());
431: return Optional.of(PREFIX_PROPERTY);
432:• } else if (c.isOWLNamedIndividual()) {
433: return Optional.of(PREFIX_INDIVIDUAL);
434: }
435: return Optional.of("");
436: }
437:
438: private String ensureUniqueIdentifier(String id) {
439: final StringBuilder sb = new StringBuilder(id);
440: while (voc.fields().containsKey(sb.toString())) {
441: sb.append("_A");
442: }
443: return sb.toString();
444: }
445:
446: /**
447: * Generates Javadoc from rdfs:comment annotation (if present).
448: *
449: * @param ontology Ontology from which the model/vocabulary is being generated
450: * @param owlEntity Annotated entity
451: * @param javaElem Element to document with Javadoc
452: * @return Whether the javadoc comment has been generated
453: */
454: private boolean generateJavadoc(OWLOntology ontology, OWLEntity owlEntity, JDocCommentable javaElem) {
455: if (!configuration.shouldGenerateJavadoc()) {
456: return false;
457: }
458: final List<OWLAnnotation> comments = EntitySearcher.getAnnotations(owlEntity, ontology)
459: .filter(a -> a.getProperty().isComment() && a.getValue()
460: .isLiteral())
461: .collect(Collectors.toList());
462: final Optional<OWLAnnotation> langComment = comments.stream().filter(a -> a.getValue().asLiteral()
463: .map(l -> l.hasLang(LANGUAGE))
464: .orElse(false)).findFirst();
465: // First try finding a comment with a matching language tag
466: if (langComment.isPresent()) {
467: langComment.flatMap(a -> a.getValue().asLiteral())
468: .ifPresent(lit -> javaElem.javadoc().add(lit.getLiteral()));
469: return true;
470: }
471: // If there is none such, just use the first available one
472: if (!comments.isEmpty()) {
473: OWLAnnotation anyComment = comments.get(0);
474: anyComment.getValue().asLiteral().ifPresent(lit -> javaElem.javadoc().add(lit.getLiteral()));
475: return true;
476: }
477: return false;
478: }
479:
480: private JDefinedClass create(final String pkg, final JCodeModel cm, final OWLClass clazz,
481: final OWLOntology ontology) {
482: JDefinedClass cls;
483:
484: String name = pkg + PACKAGE_SEPARATOR + javaClassId(ontology, clazz);
485:
486: try {
487: cls = cm._class(name);
488:
489: cls.annotate(cz.cvut.kbss.jopa.model.annotations.OWLClass.class).param("iri", entities.get(clazz));
490: cls._implements(Serializable.class);
491:
492: generateClassJavadoc(ontology, clazz, cls);
493: } catch (JClassAlreadyExistsException e) {
494: LOG.trace("Class already exists. Using the existing version. {}", e.getMessage());
495: cls = cm._getClass(name);
496: }
497: return cls;
498: }
499:
500: /**
501: * Add common properties such as id and type
502: */
503: private void addCommonClassFields(final JCodeModel cm, final JDefinedClass cls,
504: final PropertiesType propertiesType) {
505: // @Id(generated = true) protected String id;
506: final JClass ftId = cm.ref(String.class);
507: final JFieldVar fvId = addField(ID_FIELD_NAME, cls, ftId);
508: JAnnotationUse a = fvId.annotate(Id.class);
509: a.param("generated", true);
510:
511: JFieldVar fvLabel = null;
512: if (configuration.shouldGenerateAnnotationFields()) {
513: // @OWLAnnotationProperty(iri = RDFS.LABEL) String name;
514: final JClass ftLabel = cm.ref(String.class);
515: fvLabel = addField(LABEL_FIELD_NAME, cls, ftLabel);
516: fvLabel.annotate(OWLAnnotationProperty.class).param("iri", cm.ref(RDFS.class).staticRef("LABEL"));
517:
518: // @OWLAnnotationProperty(iri = DC.Terms.DESCRIPTION) String description;
519: final JClass ftDescription = cm.ref(String.class);
520: final JFieldVar fvDescription = addField(DESCRIPTION_FIELD_NAME, cls, ftDescription);
521: fvDescription.annotate(OWLAnnotationProperty.class)
522: .param("iri", cm.ref(DC.Elements.class).staticRef("DESCRIPTION"));
523: }
524:
525: // @Types Set<String> types;
526: final JClass ftTypes = cm.ref(Set.class).narrow(String.class);
527: final JFieldVar fvTypes = addField(TYPES_FIELD_NAME, cls, ftTypes);
528: fvTypes.annotate(Types.class);
529:
530: // @Properties public final Map<String,Set<String>> properties;
531: final Class<?> propertiesTypeC = (propertiesType == PropertiesType.object ? Object.class : String.class);
532: final JClass ftProperties = cm.ref(Map.class)
533: .narrow(cm.ref(String.class), cm.ref(Set.class).narrow(propertiesTypeC));
534: final JFieldVar fvProperties = addField(PROPERTIES_FIELD_NAME, cls, ftProperties);
535: fvProperties.annotate(Properties.class);
536:
537: generateToStringMethod(cls, fvId, fvLabel);
538: }
539:
540: private String javaClassId(OWLOntology ontology, OWLClass owlClass) {
541: final Optional<OWLAnnotation> res = EntitySearcher.getAnnotations(owlClass, ontology)
542: .filter(a -> isJavaClassNameAnnotation(a) &&
543: a.getValue().isLiteral()).findFirst();
544: if (res.isPresent()) {
545: return res.get().getValue().asLiteral().get().getLiteral();
546: } else {
547: return toJavaNotation(validJavaIDForIRI(owlClass.getIRI()));
548: }
549: }
550:
551: private void generateClassJavadoc(OWLOntology ontology, OWLEntity owlEntity, JDocCommentable javaElem) {
552: final boolean generated = generateJavadoc(ontology, owlEntity, javaElem);
553: if (generated) {
554: javaElem.javadoc().add("\n\n");
555: }
556: generateAuthorshipDoc(javaElem);
557: }
558:
559: private static void generateToStringMethod(JDefinedClass cls, JFieldVar idField, JFieldVar labelField) {
560: final JMethod toString = cls.method(JMod.PUBLIC, String.class, "toString");
561: toString.annotate(Override.class);
562: final JBlock body = toString.body();
563: JExpression expression = JExpr.lit(cls.name() + " {");
564: if (labelField != null) {
565: expression = expression.plus(JExpr.ref(labelField.name()));
566: }
567: expression = expression.plus(JExpr.lit("<")).plus(JExpr.ref(idField.name())).plus(JExpr.lit(">"));
568: expression = expression.plus(JExpr.lit("}"));
569:
570: body._return(expression);
571: }
572:
573: private JDefinedClass ensureCreated(final String pkg, final JCodeModel cm, final OWLClass clazz,
574: final OWLOntology ontology) {
575: if (!classes.containsKey(clazz)) {
576: classes.put(clazz, create(pkg, cm, clazz, ontology));
577: }
578: return classes.get(clazz);
579: }
580:
581: private boolean isJavaClassNameAnnotation(OWLAnnotation a) {
582: final String classNameProperty = (String) configuration.getCliParams()
583: .valueOf(Option.JAVA_CLASSNAME_ANNOTATION.arg);
584: return a.getProperty().getIRI()
585: .equals(IRI.create(classNameProperty != null ? classNameProperty : Constants.P_CLASS_NAME));
586: }
587:
588: /**
589: * Converts a class name to the Java camel case notation
590: *
591: * @param className Generated class name
592: * @return Converted class name
593: */
594: private static String toJavaNotation(String className) {
595: StringBuilder result = new StringBuilder();
596: for (String w : className.split("_")) {
597: if (!w.isEmpty()) {
598: result.append(w.substring(0, 1).toUpperCase()).append(w.substring(1));
599: }
600: }
601: return result.toString();
602: }
603: }